#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>

#include "config.h"

#include "compat.h"

#include "psd_logger.h"

#include "queue.h"

typedef struct psd_traceroute_node_t {
	LIST_ENTRY(psd_traceroute_node_t) list;
	char type;
	char *addr;
	char *host;
	char *depot;
} psdTracerouteNode;

//static const char *__psd_traceroute_lookup(const char *hostname, const char *addr);
const char *__psd_traceroute_lookup(const char *value);
static int psd_traceroute_file_read(const char *filename);
//static int psd_traceroute_find_path(const char *dest_host, char ***hosts, char ***addrs, int *path_length);
int psd_traceroute_find_path(const char *dest_host, char ***ret_list, int *ret_list_len);

const char *direct_str = "direct";
static int traceroute_max_ttl;
static int traceroute_timeout;
static char *traceroute_path = "/usr/sbin/traceroute";

static LIST_HEAD(listhead, psd_traceroute_node_t) traceroute_node_list;

int psd_traceroute_init(const char *filename, const char *new_traceroute_path, int max_ttl, int max_timeout) {
	LIST_INIT(&traceroute_node_list);

	if (psd_traceroute_file_read(filename)) {
		goto error_exit;
	}

	if (new_traceroute_path) {
		traceroute_path = strdup(new_traceroute_path);
	}

	traceroute_max_ttl = max_ttl;
	traceroute_timeout = max_timeout;

	return 0;

error_exit:
	return -1;
}

int psd_traceroute_file_read(const char *filename) {
	FILE *f;
	char line[1024];
	int i;

	f = fopen(filename, "r");
	if (!f) {
		psd_err(0, "file open failed: %s", strerror(errno));
		return -1;
	}

	bzero(line, sizeof(line));

	while(fgets(line, sizeof(line), f) != NULL) {
		char **columns;
		int column_count;
		psdTracerouteNode *new_node;
	
		i++; // increment the line number

		for(i = 0; i < sizeof(line); i++) {
			if (line[i] == '\n') {
				line[i] = '\0';
				break;
			}
		}

		columns = split_inline(line, "\t ", 1, &column_count);
		if (!columns || column_count != 3) {
			psd_err(0, "incorrect number of columns in \"%s:%d\"", filename, i);
			continue;
		}

		new_node = (psdTracerouteNode *) malloc(sizeof(psdTracerouteNode));
		if (!new_node) {
			psd_err(0, "failed to allocate memory for \"%s:%d\"", filename, i);
			continue;
		}

		if (!strcmp(columns[0], "h")) {
			new_node->type = 'h';
		} else if (!strcmp(columns[0], "i")) {
			new_node->type = 'i';
		} else if (!strcmp(columns[0], "#")) {
			continue;
		} else {
			psd_err(0, "bad type, %s, in \"%s:%d\"", columns[0], filename, i);
			free(new_node);
			continue;
		}

		new_node->addr = strdup(columns[1]);
		if (!new_node->addr) {
			free(new_node);
			psd_err(0, "failed to allocate memory for address in \"%s:%d\"", filename, i);
			continue;
		}

		new_node->depot = strdup(columns[2]);
		if (!new_node->depot) {
			free(new_node->addr);
			free(new_node);
			psd_err(0, "failed to allocate memory for hop id in \"%s:%d\"", filename, i);
			continue;
		}

		psd_info(0, "added %c \"%s\"->\"%s\"", new_node->type, new_node->addr, new_node->depot);

		LIST_INSERT_HEAD(&traceroute_node_list, new_node, list);

		free(columns);
	}

	return 0;
}

const char *psd_traceroute_lookup(const char *hop_id) {
//	char **addrs, **hosts;
	char **list;
	int length;
	const char *depot = NULL;
	int i;
	char **hi = NULL;
	int hi_count;

	hi = split(hop_id, "/", &hi_count);
	if (!hi || hi_count < 1)
		goto error_exit;

	/*
	if (psd_traceroute_find_path(hi[0], &hosts, &addrs, &length))
		goto error_exit;
	*/

	if (psd_traceroute_find_path(hi[0], &list, &length))
		goto error_exit;

	for(i = length - 1; i >= 0; i--) {
		//depot = __psd_traceroute_lookup(hosts[i], addrs[i]);
		depot = __psd_traceroute_lookup(list[i]);
		if (depot)
			break;
	}

	strlist_free(list, length);
	/*
	strlist_free(addrs, length);
	strlist_free(hosts, length);
	*/
	strlist_free(hi, hi_count);

	if (depot)
		return depot;
	else
		return direct_str;

error_exit:
	if (hi)
		strlist_free(hi, hi_count);

	return NULL;
}

//const char *__psd_traceroute_lookup(const char *hostname, const char *addr) {
const char *__psd_traceroute_lookup(const char *value) {
	psdTracerouteNode *curr;

	for(curr = traceroute_node_list.lh_first; curr != NULL; curr = curr->list.le_next) {
		if (!strcmp(value, curr->addr))
			return curr->depot;

		/*
		switch(curr->type) {
			case 'i':
				if (!strcmp(addr, curr->addr))
					return curr->depot;
				break;

			case 'h':
				if (!strcmp(hostname, curr->addr))
					return curr->depot;
				break;
		}
		*/
	}

	return NULL;
}

/* int psd_traceroute_find_path(const char *dest_host, char ***hosts, char ***addrs, int *path_length) { */
int psd_traceroute_find_path(const char *dest_host, char ***ret_list, int *ret_list_len) {
	int fds[2];
	char buf[2048];
	int i, n;
	char **list = NULL;
	int count = 0;
	/*
	char **host_list = NULL;
	int host_count = 0;
	char **addr_list = NULL;
	int addr_count = 0;
	*/
	char **lines;
	int line_count;
	pid_t p;

	if (pipe(fds))
		goto error_exit;

	p = fork();
	if (p < 0) {
		printf("Fork failed :-(\n");
	}

	if (p == 0) {
		close(STDIN_FILENO);
		dup2(fds[1], STDOUT_FILENO);
		dup2(fds[1], STDERR_FILENO);

		char max_ttl_buf[32];
		char max_timeout_buf[32];
		snprintf(max_ttl_buf, 32, "%d", traceroute_max_ttl);
		snprintf(max_timeout_buf, 32, "%d", traceroute_timeout);

		if (traceroute_max_ttl > 0 && traceroute_timeout > 1) {
			execl(traceroute_path, "traceroute", "-m", max_ttl_buf, "-w", max_timeout_buf, dest_host, NULL);
		} else if (traceroute_max_ttl > 0) { 
			execl(traceroute_path, "traceroute", "-m", max_ttl_buf, dest_host, NULL);
		} else if (traceroute_timeout > 1) { 
			execl(traceroute_path, "traceroute", "-w", max_timeout_buf, dest_host, NULL);
		} else {
			execl(traceroute_path, "traceroute", dest_host, NULL);
		}

		exit(-1);
	}

	close(fds[1]); // we just scrape the output

	n = 0;

	do {
		i = read(fds[0], buf + n, 2048 - n);
		if (i > 0) {
			n += i;
		}
	} while(i > 0);

	close(fds[0]); // close the read end of the pipe
	waitpid(p, &i, 0);	// clean up the child

	buf[n] = '\0';

	psd_info(9, "Traceroute output: %s", buf);

	lines = split_inline(buf, "\n", 1, &line_count);
	if (!lines)
		goto error_exit;

	list = NULL;
	count = 0;
	/*
	host_list = NULL;
	host_count = 0;
	addr_list = NULL;
	addr_count = 0;
	*/

	for(i = 0; i < line_count; i++) {
		char **columns;
		int column_count;

		columns = split_inline(lines[i], " ()", 1, &column_count);
		if (!columns)
			goto error_exit2;

		if (column_count >= 3) {
			int j;

		/*
			for(j = 0; j < column_count; j++) {
				if (strlist_add(columns[j], &list, &count))
					goto error_exit3;
			}
			*/

			if (strcmp(columns[1], "*") != 0 && strcmp(columns[0], "traceroute") != 0) {
				if (strlist_add(columns[1], &list, &count))
					goto error_exit3;

				if (strlist_add(columns[2], &list, &count))
					goto error_exit3;
			}
		}

		free(columns);
	}

	free(lines);

	*ret_list = list;
	*ret_list_len = count;

	/*
	*hosts = host_list;
	*addrs = addr_list;
	*path_length = host_count; // the hosts/addrs list lengths are identical
	*/

	return 0;

error_exit3:
	strlist_free(list, count);
	/*
	strlist_free(host_list, host_count);
	strlist_free(addr_list, addr_count);
	*/
error_exit2:
	free(lines);
error_exit:
	return -1;
}

int psd_traceroute_lookup_path(const char *dest_host, char ***ret_list, int *ret_list_len) {
	int fds[2];
	char buf[2048];
	int i, n;
	char **list = NULL;
	int count = 0;
	char **lines;
	int line_count;
	pid_t p;

	if (pipe(fds))
		goto error_exit;

	p = fork();
	if (p < 0) {
		return -1;
	}

	if (p == 0) {
		close(STDIN_FILENO);
		dup2(fds[1], STDOUT_FILENO);
		dup2(fds[1], STDERR_FILENO);

		char max_ttl_buf[32];
		char max_timeout_buf[32];
		snprintf(max_ttl_buf, 32, "%d", traceroute_max_ttl);
		snprintf(max_timeout_buf, 32, "%d", traceroute_timeout);

		if (traceroute_max_ttl > 0 && traceroute_timeout > 1) {
			execl(traceroute_path, "traceroute", "-m", max_ttl_buf, "-w", max_timeout_buf, dest_host, NULL);
		} else if (traceroute_max_ttl > 0) { 
			execl(traceroute_path, "traceroute", "-m", max_ttl_buf, dest_host, NULL);
		} else if (traceroute_timeout > 1) { 
			execl(traceroute_path, "traceroute", "-w", max_timeout_buf, dest_host, NULL);
		} else {
			execl(traceroute_path, "traceroute", dest_host, NULL);
		}

		exit(-1);
	}

	close(fds[1]); // we just scrape the output

	n = 0;

	do {
		i = read(fds[0], buf + n, 2048 - n);
		if (i > 0) {
			n += i;
		}
	} while(i > 0);

	close(fds[0]); // close the read end of the pipe
	waitpid(p, &i, 0);	// clean up the child

	buf[n] = '\0';

	//psd_info(9, "Traceroute output: %s", buf);

	lines = split_inline(buf, "\n", 1, &line_count);
	if (!lines)
		goto error_exit;

	list = NULL;
	count = 0;

	for(i = 0; i < line_count; i++) {
		char **columns;
		int column_count;

		columns = split_inline(lines[i], " ()", 1, &column_count);
		if (!columns)
			goto error_exit2;

		if (strcmp(columns[0], "traceroute") == 0) {
			free(columns);
			continue;
		}

		if (column_count >= 3) {
			char *endptr;
			int val;
			int n;

			// verify that the first column is a number. otherwise, it's an error msg or similar
			val = strtol(columns[0], &endptr, 10);
			if (*endptr != '\0') {
				// there's junk on the end of it
				continue;
			}

			if (strcmp(columns[1], "*") == 0) {
				// [number] * * * -- when there's nothing
				continue;
			}

			// [number] [hostname or ip] ([ip]) [num] ms [num] ms [num] ms

			if (strlist_add(columns[2], &list, &count)) {
				free(columns);
				goto error_exit3;
			}
		}

		free(columns);
	}

	free(lines);

	*ret_list = list;
	*ret_list_len = count;

	return 0;

error_exit3:
	strlist_free(list, count);
error_exit2:
	free(lines);
error_exit:
	return -1;
}
